package config

import (
	properties2 "MoSkeleton/framework/core/config/properties"
	"MoSkeleton/framework/core/consts"
	"MoSkeleton/framework/core/moerrors"
	"bytes"
	"context"
	"fmt"
	"github.com/nacos-group/nacos-sdk-go/v2/clients"
	"github.com/nacos-group/nacos-sdk-go/v2/clients/config_client"
	"github.com/nacos-group/nacos-sdk-go/v2/common/constant"
	"github.com/nacos-group/nacos-sdk-go/v2/vo"
	"github.com/spf13/viper"
	clientv3 "go.etcd.io/etcd/client/v3"
	"os"
	"path/filepath"
	"strings"
	"sync"
)

type ConfigOperLoader interface {
	LoadBootstrapConfig() (ConfigOper, error)
	LoadApplicationConfig(args ...interface{}) (ConfigOper, error)
	LoadApplicationStorageConfig(args ...interface{}) (ConfigOper, error)
}

var once sync.Once
var ConfigOperManagerLoaderInst *ConfigOperManagerLoader

type ConfigOperManagerLoader struct {
	nativeConfigLoader *NativeConfigOperLoader
	nacosConfigLoader  *NacosConfigOperLoader
	etcdConfigLoader   *EtcdConfigOperLoader
}

func (c *ConfigOperManagerLoader) LoadBootstrapConfig() (ConfigOper, error) {
	return c.nativeConfigLoader.LoadBootstrapConfig(), nil
}
func (c *ConfigOperManagerLoader) LoadApplicationConfig(args ...interface{}) (ConfigOper, error) {
	bootstrapConfig := args[0].(*properties2.BootstrapProperties)
	if bootstrapConfig.Sys.ConfigType == consts.Config_Type_Nacos {
		ok := c.nacosConfigLoader.initNacos(bootstrapConfig)
		if !ok {
			return nil, moerrors.NewWithMsg("init nacos config fail")
		}
		return c.nacosConfigLoader.LoadApplicationConfig(args...), nil
	} else if bootstrapConfig.Sys.ConfigType == consts.Config_Type_Etcd {
		ok := c.etcdConfigLoader.initEtcd(bootstrapConfig)
		if !ok {
			return nil, moerrors.NewWithMsg("init etcd config fail")
		}
		return c.etcdConfigLoader.LoadApplicationConfig(args...), nil
	} else {
		return c.nativeConfigLoader.LoadApplicationConfig(args...), nil
	}
}

func (c *ConfigOperManagerLoader) LoadApplicationStorageConfig(args ...interface{}) (ConfigOper, error) {
	bootstrapConfig := args[0].(*properties2.BootstrapProperties)
	if bootstrapConfig.Sys.ConfigType == consts.Config_Type_Nacos {
		ok := c.nacosConfigLoader.initNacos(bootstrapConfig)
		if !ok {
			return nil, moerrors.NewWithMsg("init nacos config fail")
		}
		return c.nacosConfigLoader.LoadApplicationStorageConfig(args...), nil
	} else if bootstrapConfig.Sys.ConfigType == consts.Config_Type_Etcd {
		ok := c.etcdConfigLoader.initEtcd(bootstrapConfig)
		if !ok {
			return nil, moerrors.NewWithMsg("init etcd config fail")
		}
		return c.etcdConfigLoader.LoadApplicationStorageConfig(args...), nil
	} else {
		return c.nativeConfigLoader.LoadApplicationStorageConfig(args...), nil
	}
}

// ---NacosConfigOperLoader
type NacosConfigOperLoader struct {
	isInit       bool
	configClient config_client.IConfigClient
}

func (n *NacosConfigOperLoader) initNacos(bootstrapConfig *properties2.BootstrapProperties) bool {
	if n.isInit {
		return true
	}
	if len(bootstrapConfig.Nacos.Config.Server) == 0 {
		return false
	}
	scs := make([]constant.ServerConfig, 0)
	for _, server := range bootstrapConfig.Nacos.Config.Server {
		scs = append(scs, constant.ServerConfig{
			IpAddr:      server.IpAddr,
			Port:        server.Port,
			ContextPath: server.ContextPath,
			Scheme:      server.Scheme,
			GrpcPort:    server.GrpcPort,
		})
	}
	client := bootstrapConfig.Nacos.Config.Client
	cc := constant.ClientConfig{
		NamespaceId:         client.NamespaceId,
		TimeoutMs:           client.TimeoutMs,
		NotLoadCacheAtStart: true,
		LogDir:              client.LogDir,
		CacheDir:            client.CacheDir,
		LogLevel:            client.LogLevel,
		Username:            client.Username,
		Password:            client.Password,
	}

	configClient, err := clients.CreateConfigClient(map[string]interface{}{
		"serverConfigs": scs,
		"clientConfig":  cc,
	})
	if err != nil {
		panic(err)
	}
	n.configClient = configClient
	n.isInit = true
	return true
}

func (n *NacosConfigOperLoader) LoadApplicationConfig(args ...interface{}) ConfigOper {
	var bootstrapConfig *properties2.BootstrapProperties
	bootstrapConfig = args[0].(*properties2.BootstrapProperties)

	return n.loadConfigByDataId(bootstrapConfig, consts.ApplicationDataId)
}

func (n *NacosConfigOperLoader) LoadApplicationStorageConfig(args ...interface{}) ConfigOper {
	var bootstrapConfig *properties2.BootstrapProperties
	bootstrapConfig = args[0].(*properties2.BootstrapProperties)
	return n.loadConfigByDataId(bootstrapConfig, consts.ApplicationStorageDataId)
}

func (n *NacosConfigOperLoader) loadConfigByDataId(bootstrapConfig *properties2.BootstrapProperties, dataId string) ConfigOper {
	for _, configClientConfig := range bootstrapConfig.Nacos.Config.Client.Configs {
		if configClientConfig.DataId == dataId {
			content, err := n.configClient.GetConfig(vo.ConfigParam{
				DataId: configClientConfig.DataId,
				Group:  configClientConfig.Group,
			})
			if err != nil {
				panic(err)
			}
			configOper := NewDefaultConfigOper()
			if err := configOper.configInst.ReadConfig(strings.NewReader(content)); err != nil {
				panic(err)
			}
			return configOper
		}
	}
	return nil
}

// ------NativeConfigOperLoader
type NativeConfigOperLoader struct {
}

func (n *NativeConfigOperLoader) LoadBootstrapConfig() ConfigOper {
	configFile := filepath.Join(consts.ConfigPath, consts.BootstrapDataId)
	return NewByFile([]string{}, configFile)
}

func (n *NativeConfigOperLoader) LoadApplicationConfig(args ...interface{}) ConfigOper {
	configFile := filepath.Join(consts.ConfigPath, consts.ApplicationDataId)
	return NewByFile([]string{}, configFile)
}

func (n *NativeConfigOperLoader) LoadApplicationStorageConfig(args ...interface{}) ConfigOper {
	configFile := filepath.Join(consts.ConfigPath, consts.ApplicationStorageDataId)
	return NewByFile([]string{}, configFile)
}

// ----- etcd

type EtcdConfigOperLoader struct {
	isInit bool
	client *clientv3.Client
}

func (e *EtcdConfigOperLoader) initEtcd(bootstrapConfig *properties2.BootstrapProperties) bool {
	if e.isInit {
		return true
	}
	var err error
	e.client, err = clientv3.New(clientv3.Config{
		Endpoints:   bootstrapConfig.Etcd.Endpoints,
		DialTimeout: bootstrapConfig.Etcd.DialTimeout,
		Username:    bootstrapConfig.Etcd.Username,
		Password:    bootstrapConfig.Etcd.Password,
	})
	if err != nil {
		panic(err)
	}
	e.isInit = true
	return true
}
func (e *EtcdConfigOperLoader) LoadApplicationConfig(args ...interface{}) ConfigOper {
	var bootstrapConfig *properties2.BootstrapProperties
	bootstrapConfig = args[0].(*properties2.BootstrapProperties)
	return e.loadConfigByKey(bootstrapConfig, consts.ApplicationDataId)
}

func (e *EtcdConfigOperLoader) LoadApplicationStorageConfig(args ...interface{}) ConfigOper {
	var bootstrapConfig *properties2.BootstrapProperties
	bootstrapConfig = args[0].(*properties2.BootstrapProperties)
	return e.loadConfigByKey(bootstrapConfig, consts.ApplicationStorageDataId)
}

func (e *EtcdConfigOperLoader) loadConfigByKey(bootstrapConfig *properties2.BootstrapProperties, configKey string) ConfigOper {
	ctx, cancel := context.WithTimeout(context.Background(), bootstrapConfig.Etcd.DialTimeout)
	defer cancel()
	resp, err := e.client.Get(ctx, configKey)
	if err != nil {
		panic(err)
	}

	if len(resp.Kvs) == 0 {
		panic(fmt.Sprintf("key %s not found in etcd", configKey))
	}

	value := resp.Kvs[0].Value // 从 resp 中获取值
	configOper := NewDefaultConfigOper()
	if err := configOper.configInst.ReadConfig(bytes.NewBuffer(value)); err != nil {
		panic(err)
	}
	return configOper
}

// filenames 不带后缀
func NewByFilename(paths []string, filename string) ConfigOper {
	return newBy(paths, "", filename)
}

// files带后缀
func NewByFile(paths []string, file string) ConfigOper {
	return newBy(paths, file, "")
}

func newBy(paths []string, file string, filename string) ConfigOper {
	configOper := NewDefaultConfigOper()
	for _, path := range paths {
		if _, err := os.Stat(path); err != nil {
			panic(fmt.Sprintf("config path: %s", err.Error()))
		}
		configOper.configInst.AddConfigPath(path)
	}
	if file != "" {
		configOper.configInst.SetConfigFile(file)
	}
	if filename != "" {
		configOper.configInst.SetConfigName(filename)
	}

	if err := configOper.configInst.ReadInConfig(); err != nil {
		if _, ok := err.(viper.ConfigFileNotFoundError); ok {
			panic(fmt.Sprintf("lost config file: %s", err.Error()))
		} else {
			panic(err.Error())
		}
	}
	return configOper
}

func init() {
	once.Do(func() {
		ConfigOperManagerLoaderInst = &ConfigOperManagerLoader{
			nativeConfigLoader: &NativeConfigOperLoader{},
			nacosConfigLoader:  &NacosConfigOperLoader{},
			etcdConfigLoader:   &EtcdConfigOperLoader{},
		}
	})
}
